﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Windows.Forms;
using CnSharp.SSMS.AddIn.Framework.AddinControls;
using CnSharp.SSMS.AddIn.Util;
using EnvDTE;
using Microsoft.SqlServer.Management;
using Microsoft.SqlServer.Management.SqlStudio.Explorer;
using Microsoft.SqlServer.Management.UI.VSIntegration;
using Microsoft.SqlServer.Management.UI.VSIntegration.ObjectExplorer;
using Microsoft.VisualStudio.CommandBars;

namespace CnSharp.SSMS.AddIn.Framework
{
    public class CommandManager
    {
        private CommandConfig _config;
        private static readonly Dictionary<string, Form> _forms = new Dictionary<string, Form>();
        private readonly EnvDTE.AddIn _addInInstance;
        //private readonly IList<CommandBarButton> _buttons = new List<CommandBarButton>();
        private readonly IList<Command> _commands = new List<Command>();
        private readonly IList<string> _objectContextMenus = new List<string>(); 
        private readonly Assembly _entryAssembly;
        public ILogger Logger { get; set; }
        public CommandConfig CommandConfig{get { return _config; }}


        //private readonly Dictionary<string, HierarchyObject> _menuRegister = new Dictionary<string, HierarchyObject>();
        private readonly IList<CommandBarControl> _menus = new List<CommandBarControl>();

        public ObjectContextEventHandler ObjectContextEventHandler;
        private ContextService _contextService;
        private ResourceManager _resourceManager;


        public CommandManager(Assembly entryAssembly, CommandConfig commandConfig, EnvDTE.AddIn addInInstance)
        {
            _entryAssembly = entryAssembly;

            _addInInstance = addInInstance;

            _config = commandConfig;

            _resourceManager = new ResourceManager(_config.ResourceManager, _entryAssembly);
        }

        //public static AddIn AddIn { set; get; }


        public void Load()
        {
            try
            {
                _config.Buttons.ForEach(AddButton);
                _config.Menus.ForEach(AddMenu);
                _config.ContextMenus.ForEach(AddMenu);
            }
            catch(Exception e)
            {
                Logger.WriteLog(e.Message+Environment.NewLine+e.StackTrace);
            }

            //var objExplorerService =
            //    (ObjectExplorerService) ServiceCache.ServiceProvider.GetService(typeof (IObjectExplorerService));
            //if (objExplorerService.Container != null)
            //{
            //    _contextService = (ContextService) objExplorerService.Site.Component;
            //    //_contextService.ObjectExplorerContext.CurrentContextChanged
            //    _contextService.ActionContext.CurrentContextChanged += ActionContextOnCurrentContextChanged;
            //}

            _contextService = (ContextService) ServiceCache.ServiceProvider.GetService(typeof (IContextService));
            _contextService.ActionContext.CurrentContextChanged += ActionContextOnCurrentContextChanged;
        }


        private void ActionContextOnCurrentContextChanged(object sender, EventArgs eventArgs)
        {
            try
            {
                var node = ObjectExplorerHelper.GetSelectedNode();
                if (node == null)
                    return;

                var group = ObjectContextMenuGroupHelper.JudgeMenuGroup(node);

                AddObjectExplorerContextMenu(group, node);

            }
            catch (Exception ex)
            {
                Logger.WriteLog(ex.Message + Environment.NewLine + ex.StackTrace);
            }
        }

       
        private Command AddCommond(CommandControl menu)
        {
            var contextUIGuids = new object[] {};
            Command cmd = null;
            var dte = ServiceCache.ExtensibilityModel;
            try
            {
                cmd = ServiceCache.ExtensibilityModel.Commands.Item(_addInInstance.ProgID + "." + menu.Id, -1);
            }
            catch//必须手工处理，否则debug不通过
            {
                //If we are here, then the exception is probably because a command with that name
                //      already exists. If so there is no need to recreate the command and we can 
                //      safely ignore the exception.
            }
            //if (cmd != null)
            //    cmd.Delete();
            if (cmd == null)
            {
            //if (!string.IsNullOrWhiteSpace(menu.Picture))
            //    {
            //        var picture = LoadPicture(_resourceManager, menu.Picture);
                   
            //        cmd = ((Commands2)dte.Commands).AddNamedCommand2(_addInInstance,
            //                                                          menu.Id, menu.Text, menu.Tooltip,
            //                                                          false, menu.Picture, ref contextUIGuids
            //            );
            //    }
            //    else
            //    {
                    cmd = dte.Commands.AddNamedCommand(_addInInstance,
                                                                     menu.Id, menu.Text, menu.Tooltip,
                                                                     true, menu.FaceId, ref contextUIGuids,
                                                                     (int)
                                                                     (vsCommandStatus.vsCommandStatusSupported |
                                                                      vsCommandStatus.vsCommandStatusEnabled)
                       );
                //}

            }
            //if (!string.IsNullOrEmpty(menu.HotKey))
            //{

            //    var props = dte.Properties["Environment", "Keyboard"];
            //    var scheme = props.Item("Scheme");
            //    scheme.Value = Application.StartupPath + "\\keyboard.vsk";
            //    cmd.Bindings = string.Format("Global::{0}", menu.HotKey);
            //}
            _commands.Add(cmd);
            return cmd;
        }

        //private void AddContextMenu(CommandControl menu)
        //{
        //    var cmd = AddCommond(menu);
        //    AddCommandBarButton(menu, cmd);
        //}

        private CommandBarButton AddCommandBarButton(CommandControl menu, Command cmd)
        {
            var cmdBars = (CommandBars) ServiceCache.ExtensibilityModel.CommandBars;
            var bar = cmdBars[menu.AttachTo];
            var controls = bar.Controls.GetEnumerator();

            while (controls.MoveNext())
            {
                if (((CommandBarControl) controls.Current).Caption == menu.Text)
                {
                    ((CommandBarControl) controls.Current).Delete(false);
                    break;
                }
            }

            var btn = (CommandBarButton) cmd.AddControl(bar, bar.Controls.Count + menu.Position);


            FormatCommandBarButton(menu, btn);
            _menus.Add(btn);

            //if (menu.Id == "browseTemplateFolder")
            //{
            //    btn.Click += btn_Click;
            //}

            return btn;
        }

        //void btn_Click(CommandBarButton ctrl, ref bool cancelDefault)
        //{
        //    var popup = ctrl.Parent;
        //    MessageBox.Show(popup.get_accValue());
        //}


        private void AddObjectExplorerContextMenu(ObjectContextMenuGroup group, INodeInformation node)
        {
            if (group == ObjectContextMenuGroup.Other)
                return;


            var objMenu = (HierarchyObject)node.GetService(typeof(IMenuHandler));

            foreach (var menu in _config.ObjectContextMenus)
            {
                if (!_objectContextMenus.Contains(menu.Id) && menu.Group == group)
                {

                    _objectContextMenus.Add(menu.Id);
                     
                    var menuItem = GetMenuItem(menu.ClassName);

                    objMenu.AddChild(menu.Id, menuItem);
                }
            }


            if (ObjectContextEventHandler != null)
            {
                ObjectContextEventHandler(group, node);
            }
        }


        private void AddMenu(CommandMenu menu)
        {


            var mainMenu = AddMainMenu(menu);
            if (menu.SubMenus.Count > 0)
            {
                var toolsPopup = (CommandBarPopup)mainMenu;
                menu.SubMenus.ForEach(m => AddSubMenu(toolsPopup, m));
            }

        }

        public CommandBarControl AddMainMenu(CommandMenu menu)
        {
            CommandBarControl btn;

            if (string.IsNullOrWhiteSpace(menu.AttachTo))
            {
                menu.AttachTo = "MenuBar";
                var mainMenu = ((CommandBars) ServiceCache.ExtensibilityModel.CommandBars)[menu.AttachTo];

                var controls = mainMenu.Controls.GetEnumerator();

                while (controls.MoveNext())
                {
                    if (((CommandBarControl) controls.Current).Caption == menu.Text)
                    {
                        ((CommandBarControl) controls.Current).Delete(true);
                        break;
                    }
                }

                btn = mainMenu.Controls.Add(MsoControlType.msoControlPopup, Type.Missing,
                                            Type.Missing,
                                            mainMenu.Controls.Count + menu.Position,
                                            true)
                    ;
            }
            else
            {
                var cmd = AddCommond(menu); 
                 btn = AddCommandBarButton(menu, cmd);
            }
            btn.Caption = menu.Text;
            btn.Visible = true;

            return btn;
        }


        private void AddSubMenu(CommandBarPopup parentMenu, CommandMenu menu)
        {
            var cmd = AddCommond(menu);
            var bar =
                (CommandBarButton)cmd.AddControl(parentMenu.CommandBar, parentMenu.Controls.Count + menu.Position);

            FormatCommandBarButton(menu, bar);
         

            _menus.Add(bar);

            if (menu.SubMenus != null && menu.SubMenus.Count > 0)
            {
                menu.SubMenus.ForEach(sub => AddSubMenu((CommandBarPopup) bar, sub));
            }

       
        }

        private void AddButton(CommandButton button)
        {
      
            var cmd = AddCommond(button); //
            var btn = AddCommandBarButton(button, cmd);
            btn.Style = MsoButtonStyle.msoButtonIcon;
        }

        private void FormatCommandBarButton(CommandControl button, CommandBarButton btn)
        {
            btn.Caption = button.Text;
            if (!String.IsNullOrEmpty(button.Picture))
            {
                btn.Picture = _resourceManager.LoadPicture(button.Picture);
            }
            else
            {
                if (button.FaceId <= 0)
                    btn.Style = MsoButtonStyle.msoButtonCaption;
            }
            btn.Visible = true;
            btn.BeginGroup = button.BeginGroup;
        }

      

        private ToolsMenuItemBase GetMenuItem(string name)
        {
            return (ToolsMenuItemBase) LoadInstance(name);
            //ToolsMenuItemBase menu =   (ToolsMenuItemBase)obj;
            //if(menu is SchemaMenuItem)
            //{
            //    (menu as SchemaMenuItem).Dte = dte;
            //}
            //return menu;
        }

        private ICommand GetCommand(string name)
        {
            //var type = _entryAssembly.GetType(name);

            return (ICommand) LoadInstance(name);
        }

        private object LoadInstance(string typeName)
        {
            try
            {
                if (typeName.Contains(","))
                {
                    var arr = typeName.Split(',');
                    if (arr.Length < 2)
                        return null;
                    var assemblyName = arr[1];
                    var assembly = Assembly.Load(assemblyName);
                    return assembly.CreateInstance(arr[0]);
                }
                return _entryAssembly.CreateInstance(typeName);
            }
            catch (Exception exception)
            {
                Logger.WriteLog(String.Format("Load type [{0}] failed:{1}\r\n{2}", typeName, exception.Message,
                                              exception.StackTrace));
                return null;
            }
        }

        private Form GetForm(string name)
        {
            if (_forms.ContainsKey(name))
            {
                if (!_forms[name].IsDisposed)
                    return _forms[name];
                _forms.Remove(name);
            }
            //   var type = _entryAssembly.GetType(name);
            var form = (Form) LoadInstance(name);
            _forms.Add(name, form);
            return form;
        }

        private void Execute(CommandControl menu)
        {
            switch (menu.CommandActionType)
            {
                case CommandActionType.program:
                    var command = GetCommand(menu.ClassName);
                    command.Execute();
                    break;
                case CommandActionType.window:
                    var window = GetForm(menu.ClassName);
                    window.Show();
                    break;
                case CommandActionType.dialog:
                    var dialog = GetForm(menu.ClassName);
                    dialog.ShowDialog();
                    break;
            }
        }

        public void Execute(string commandName)
        {
            var menu = FindCommandMenu(_config.Menus, commandName);
            if (menu != null)
            {
                Execute(menu);
                return;
            }
            foreach (var m in _config.Buttons)
            {
                if (String.Compare(m.Id, commandName, true) == 0)
                {
                    Execute(m);
                    return;
                }
            }
            foreach (var m in _config.ContextMenus)
            {
                if (String.Compare(m.Id, commandName, true) == 0)
                {
                    Execute(m);
                    return;
                }
            }
        }

        private CommandMenu FindCommandMenu(List<CommandMenu> menus, string commandName)
        {
            foreach (CommandMenu m in menus)
            {
                if (String.Compare(m.Id, commandName, true) == 0)
                    return m;
                if (m.SubMenus.Count > 0)
                    return FindCommandMenu(m.SubMenus, commandName);
            }
            return null;
        }

        public void Disconnect()
        {
            foreach (var command in _commands)
            {
                command.Delete();
            }
            //foreach (var button in _buttons)
            //{
            //    button.Delete(true);
            //}
            foreach (var menu in _menus)
            {
                try
                {
                    menu.Delete(true);
                }
                catch
                {
                    
                }
            }

            
        }

        private ObjectContextEventHandler _foo;
        public event ObjectContextEventHandler ObjectContextChanged
        {
            add
            {
                if (_foo == null || !_foo.GetInvocationList().Contains(value))
                {
                    _foo += value;
                }
            }
            remove
            {
                _foo -= value;
            }
        }

    }



    public delegate void ObjectContextEventHandler(ObjectContextMenuGroup group, INodeInformation node);
}



  